package mnet

/*
 * mnet server
 */

import (
	"net"
	"runtime"
	"time"
)

type mServer struct {
	sessionFactory ISessionFactory
	codec          ICodec
	attach         interface{}
	lockThread     bool
	addr           string
	ipVersion      string
	netListener    *net.TCPListener
	maxConnection  int
}

// newServer creates a server object.
func newServer(ipVersion, addr string,
	sessionFactory ISessionFactory, codec ICodec,
	attach interface{}, lockThread bool, maxConn int,
) IServer {
	if sessionFactory == nil {
		panic("session factory is nil")
	}

	if codec == nil {
		panic("codec is nill")
	}

	server := &mServer{
		sessionFactory: sessionFactory,
		codec:          codec,
		attach:         attach,
		lockThread:     lockThread,
		addr:           addr,
		ipVersion:      ipVersion,
		netListener:    nil,
		maxConnection:  maxConn,
	}
	return server
}

func (ms *mServer) StartListen() bool {
	// Resolve tcp address
	addr, err := net.ResolveTCPAddr(ms.ipVersion, ms.addr)
	if err != nil {
		log().Error("[net] Resolve tcp addr error:", err)
		return false
	}

	// Try listen on addr
	var listenErr error
	ms.netListener, listenErr = net.ListenTCP(ms.ipVersion, addr)
	if listenErr != nil {
		log().Error("[net] listen on ", addr, " error:", err)
		return false
	}
	log().Info("[net] listen on ", addr, " success")

	go func() {
		// Try to lock ms goroutine to os thread.
		if ms.lockThread {
			runtime.LockOSThread()
			defer runtime.UnlockOSThread()
		}

		// Try to accept tcp connection
		for {
			if conn, err := ms.netListener.AcceptTCP(); err == nil {
				netModule := GetNetInstance()
				if ms.maxConnection != 0 && netModule.getConnMgr().len() > ms.maxConnection {
					log().Error("[net] connection is over ", ms.maxConnection, " connections")
					conn.Close()
					continue
				}

				// Create tcp session.
				session := ms.sessionFactory.CreateSession()
				if session == nil {
					log().Error("[net] create session is nil")
					continue
				}

				if tcpConn := netModule.getConnMgr().create(netModule, conn, session, ms.codec); tcpConn != nil {
					go tcpConn.start()
				} else {
					log().Error("[net] netModule.getConnMgr().create return nil")
				}
			} else {
				log().Error("[net] TCP accept error:", err)
				return
			}
		}
	}()

	return true
}

// GetAttach get attach.
func (ms *mServer) GetAttach() interface{} {
	return ms.attach
}

// stop server:set listen timeout, so the goroutine exit.
func (ms *mServer) Stop() {
	ms.netListener.SetDeadline(time.Now())
}
